package hyl.prod.monitor;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.zookeeper.WatchedEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.esotericsoftware.minlog.Log;

import hyl.base.cache.redis.CRedis;
import hyl.base.cache.redis.RedisPool;
import hyl.base.zk.IZKEvent;
import hyl.base.zk.MyZK;
import hyl.base.zk.ZkCooperation;
import hyl.core.Amy;
import hyl.core.MyDate;
import hyl.core.MyFun;
import hyl.core.conf.MyConfig;
import hyl.core.io.MyFile;
import hyl.core.io.MyFileListener;
import hyl.core.io.MyPath;
import hyl.core.net.MyHttp;
import hyl.core.net.MyMail;
import hyl.core.net.MySms;
import hyl.core.net.MySocket;
import hyl.core.run.IFilter;
import hyl.core.run.MyRun;

public class Monitor implements Runnable {

	private static final Logger logger = LoggerFactory.getLogger("Monitor");
	Map<String, CheckItem> _ciMap = new HashMap<>();
	static int 检查周期_秒 = 10; //

	String CMD_MODEL = null;
	String CMD_KILL = null;
	String CMD_CLOSEWIN = null; // 关闭窗口的命令 window独有

	String s系统 = null;
	static final String s主要配置 = "monitor_cmd.json";
	static final String BASEPATH = MyPath.getPathOfPro();

	/*
	 * zk 在这个项目中有两个用途 1. 防止自己掉线,如果自己掉线 立即通知服务器 2. 防止被监控的对象掉线
	 */
	ZkCooperation _zkc监控者 = null;
	MyZK _mzk = null;
	// 网络 连接超时
	static final int connecttimeout = 5000;
	String _ip = null;
	String _id = null;
	String s描述 = null;

	String s邮件通知 = null;

	MyMail _sml = null;

	JSONObject _消息模板 = null;
	//
	static final String s默认rd配置 = "redis1";
	RedisPool _redispool = RedisPool.getInstance();
	CRedis _redis = null;
	//配置文件监听器
	MyFileListener mfl=null;
	String s检查项 = "";
	// 离线事件
	IZKEvent offlineHandler = new IZKEvent() {
		@Override
		public void run(WatchedEvent event, List<String> 在网, List<String> 交集, List<String> 新增, List<String> 离网) {
			if (离网 != null && !离网.isEmpty()) {
				// 发现有人离网后,只有主节点可以负责发送消息，避免重复发送
				if (交集.get(0).equals(_zkc监控者.getPID())) {
					send哨兵通知(离网, "哨兵离网", true);
				}
			}
			if (新增 != null && !新增.isEmpty()) {
				// 发现有人离网后,只有主节点可以负责发送消息，避免重复发送
				if (交集.get(0).equals(_zkc监控者.getPID())) {
					send哨兵通知(在网, "哨兵更新", false);
				}
			}

		}
	};
	int 重试几次提醒 = 1;
	int 最多提醒次数 = 1;
	String DIR_SHELLBASE = MyPath.find("shell");
	JSONObject _conf;
	String zkbase=null;
	String zkurl =null;
	int zktimeout =0;
	//系统命令模板
	JSONObject _sysmdl ;
	
	public Monitor() throws IOException {

		_ip = MyHttp.get外网IP()[0];
		// DIR_BASE=MyPath.getPathOfClasses(this.getClass()).substring(1);
		// System.out.println(MyPath.find("shell"));
		// System.out.println(DIR_BASE);
		// DIR_BASE="./";

		//////////////////////////////// 主配置文件////////////////////////////

		JSONObject conf = (JSONObject) MyConfig.loadJson(s主要配置, "utf-8");
		String path1 = BASEPATH + s主要配置;
		//System.out.println(path1);
		_conf = conf.getJSONObject("环境");
		 zkbase = _conf.getString("ZK项目目录");
		 zkurl = _conf.getString("ZK服务器");
		 zktimeout = _conf.getIntValue("ZK超时_毫秒");
		重试几次提醒 = _conf.getIntValue("重试几次提醒");
		最多提醒次数 = _conf.getIntValue("最多提醒次数");
		检查周期_秒 = _conf.getIntValue("检查周期(秒)");
		if (检查周期_秒 == 0)
			检查周期_秒 = 5;
		s系统 = _conf.getString("当前系统");
		_id = _conf.getString("ID");
		s描述 = _conf.getString("描述");
		s邮件通知 = _conf.getString("邮件通知对象");

		if (MyFun.isEmpty(_id)) {
			_id = _ip;
			_conf.put("ID", _id);

			// MyConfig.writeJson(path1, conf);// ??
		}
		_消息模板 = conf.getJSONObject("消息模板");
		// _消息模板.getString("哨兵离网");

		_sysmdl = conf.getJSONObject(s系统);

////////////////////////////////子配置文件////////////////////////////

		CMD_MODEL = _sysmdl.getString("处理");
		CMD_CLOSEWIN = _sysmdl.getString("关闭");
		
		String cfgfile = _sysmdl.getString("配置文件");
		String cfgfilepath=  MyPath.find(cfgfile);
		mfl=new MyFileListener(cfgfilepath);
		//如果配置文件发生变更 重新 载入
		mfl.setFileChangedEvent(3000, new IFilter<File>() {

			@Override
			public boolean where(File obj) {
				try {
					load配置文件(cfgfile);
				} catch (IOException e) {
					
					e.printStackTrace();
				}
				return false;
			}

		});
		
		load配置文件(cfgfile);
////////////////////////////短信和邮箱组件初始化////////////////////////////////////////////
		_sml = MyMail.getInstance("zdy");

	}
	
	void load配置文件(String cfgfilepath) throws IOException {
		JSONObject mp = (JSONObject) MyConfig.loadJson(cfgfilepath, Amy.Charset);
		
		// 把监控服务注册到 redis 4号库 的key-value中 方便查询和管理  //////
				MyConfig.ini();
				try {

					_redis = _redispool.getRedis(s默认rd配置, 4);

		// 如果已经存在,覆盖
					_redis.hset(_id, "环境变量", _conf.toJSONString());
					_redis.hset(_id, "系统变量", _sysmdl.toJSONString());
					_redis.hset(_id, "检查项", mp.toJSONString());
				} catch (Exception e) {
					logger.error("redis服务器无法连接 ");
					// e.printStackTrace();

				}
		// 监控zk上的变化 一旦有同类离线 在线的第一个监控人立即执行回调函数
		// 如果无法连接zk 服务器,就独立工作
				try {
					if (_redis != null) {
						if (MySocket.checkAddress(zkurl, zktimeout)) {
							_mzk = new MyZK(zkurl, zktimeout);
							_zkc监控者 = new ZkCooperation(zkbase, _mzk);
							_zkc监控者._handler = offlineHandler;
							_redis.hset("ZKTID", _zkc监控者.getPID(), _id);
						}
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

		////////////////////////////////解析 检查项///////////////////////////////////
				StringBuilder sb = new StringBuilder();
				for (Map.Entry<String, Object> pp : mp.entrySet()) {
					JSONObject p = (JSONObject) pp.getValue();
					// 每个检查块都不一样
					CheckItem ci = new CheckItem();
					ci._name = pp.getKey();
					// ci.set通知(p.getString("通知方式"));
					ci._errcmd = do解析命令(p.getString("异常回调"));
					ci._succcmd = do解析命令(p.getString("成功回调"));
					ci.set检查方式(p.getString("检查方式"));
					String cmd = p.getString("检查连接");
					if (!MyFun.isEmpty(cmd)) {
						ci._类型 = CheckItem.I_连接;
						ci._检查内容 = cmd.split(";");
					} else {
						cmd = p.getString("检查进程");
						if (!MyFun.isEmpty(cmd)) {
							ci._检查内容 = new String[] { _sysmdl.getString("检查进程").replace("$1", cmd),cmd };
							ci._类型 = CheckItem.I_进程;
						} else {
							cmd = p.getString("检查端口");
							if (!MyFun.isEmpty(cmd)) {
								ci._检查内容 = cmd.split(";");
								ci._类型 = CheckItem.I_端口;
							} else {
								cmd = p.getString("检查文件");
								if (!MyFun.isEmpty(cmd)) {
									ci._检查内容 = cmd.split(";");
									ci._类型 = CheckItem.I_文件;
								} else {
									String s = p.getString("检查RDSK");
									if (!MyFun.isEmpty(s)) {
										ci._检查内容 = s.split(";");
										ci._类型 = CheckItem.I_RDSK;
									} else {
										ci = null;
										continue;
									}
								}
							}
						}
					}
					if (ci != null) {
						_ciMap.put(ci._name, ci);
						sb.append(ci._name).append(",");
					}
				}
				if (sb.length() > 0) {
					sb.deleteCharAt(sb.length() - 1);
				}
				s检查项 = sb.toString();
	}

	String do解析命令(String str) {
		if (MyFun.isEmpty(str))
			return null;
		String s = CMD_MODEL.replace("$1", DIR_SHELLBASE);
		s = s.replace("$2", str);
		// System.out.println(s);
		return s;
	}

	public String get检查项集() {
		return s检查项;
	}

	// 哨兵出异常 一定要短信通知
	void send哨兵通知(List<String> 哨兵们, String 模板, boolean 是否短信通知) {
		String msg = _消息模板.getString(模板);
		Map<String, String> mp = _redis.hgetall("ZKTID");
		List<String> idli = new ArrayList<>();
		List<String> conli = new ArrayList<>();
		for (String s : 哨兵们) {
			String id = mp.get(s);
			idli.add(id);
			String content = _redis.hget(id, "环境");
			conli.add(content);
		}
		msg = msg.replace("$1", MyFun.array2Str(idli.toArray()));
		String mailcontent = MyFun.array2Str(conli.toArray(), "\r\n");

		String title = MyFun.join(MyFun.getNow(), msg);
		// 发出企业邮件
		if (null != _sml) {
			_sml.send(s邮件通知, title, mailcontent);
		}
		// 记录日志
		Log.error(title);
	}

	void send程序通知(CheckItem 项目, String 消息模板) {
		String msg = _消息模板.getString(消息模板);
		msg = msg.replace("$1", _id);
		msg = msg.replace("$2", 项目._name);

		String title = MyFun.array2Str(MyFun.getNow(), msg);
		// 发出企业邮件(项目._通知 & 1) == 1 &&
		if (null != _sml) {
			_sml.send(s邮件通知, title, _ciMap.get(项目._name).toString());
		}
		// 记录日志
		Log.error(title);
	}

	// 解析 成功或异常的处理指令
	/*
	 * public List<Command> setCmds(JSONArray source) { if (source == null) return
	 * null; List<Command> cmds = new ArrayList<Command>(); for (Object obj :
	 * source) { String str = (String) obj; if (str.startsWith("执行")) { String s =
	 * str.substring(3).trim(); cmds.add(new Command(1, s)); } else if
	 * (str.startsWith("等待")) { Integer time =
	 * MyFun.str2int(str.substring(3).trim()); // MyDate.sleep( time*1000);
	 * cmds.add(new Command(0, time)); } } return cmds; }
	 */

	public void run() {
		logger.info("好运来哨兵已经启动,正在监控...");
		while (true) {
			for (Map.Entry<String, CheckItem> ci : _ciMap.entrySet()) {
				boolean is运行中 = true;
				CheckItem cb = ci.getValue();
				// System.out.println(1);
				if (cb._类型 == CheckItem.I_连接)
					is运行中 = testUrls(cb);
				else if (cb._类型 == CheckItem.I_进程)
					is运行中 = testProcess(cb);
				else if (cb._类型 == CheckItem.I_端口)
					is运行中 = testIpPorts(cb);
				else if (cb._类型 == CheckItem.I_文件)
					is运行中 = testFiles(cb);
				else if (cb._类型 == CheckItem.I_RDSK) {
					is运行中 = testRDSKs(cb);
				} else {// 不满足 跳过
					continue;
				}

				// System.out.println(2);
				// 如果没有运行 就是异常
				if (!is运行中) {
					cb._异常次数++;
					if (cb._异常次数 > 重试几次提醒) {
						cb._异常次数 = 1;
					}
					if (cb._异常次数 == 1 && cb._提醒次数 < 最多提醒次数) {
						if (cb._errcmd != null) {
							logger.error( MyRun.cmd(cb._errcmd));
						}
						send程序通知(cb, "程序异常");
						cb._提醒次数++;
					}
				} else {// 如果正在运行
					if (cb._提醒次数 > 0 || cb._异常次数 > 0) {
						send程序通知(cb, "程序恢复");
						cb._提醒次数 = 0;
						cb._异常次数 = 0;
					}
				}
			}

			// System.out.println("结束线程"+79/0);

			MyDate.sleep(检查周期_秒 * 1000);
			// System.out.println(3);

		}
	}

	/**
	 * 检查本地程序 如果在运行 就返回true,如果不在返回false
	 * 
	 * @param cmd
	 * @return
	 */
	public boolean testProcess(CheckItem ci) {
		String cmd = ci._检查内容[0];
		String[] ss =  ci._检查内容[1].split(" ");
		String rs = MyRun.cmd(cmd);
		//System.out.println(rs);
		if (ci.i检查方式 == CheckItem.I_关系并且) {
			for (String s1 : ss) {
				//System.out.println(">>> rs="+rs+"\r\n s1="+s1+"  rs.indexOf(s1) ="+rs.indexOf(s1) );
				if (rs.indexOf(s1) < 0)
					return false;
			}
			return true;
		} else {
			for (String s1 : ss) {
				if (rs.indexOf(s1) >= 0)
					return true;
			}
			return false;
		}
	}

	/**
	 * 检查所有redis 项
	 * 
	 * @param ci 检查项
	 * @return
	 */
	boolean testRDSKs(CheckItem ci) {
		String cmd[] = ci._检查内容;
		if (ci.i检查方式 == CheckItem.I_关系并且) {
			for (String s : cmd) {
				if (!testRDSK(s))
					return false;
			}
			return true;
		} else {
			for (String s : cmd) {
				if (testRDSK(s))
					return true;
			}
			return false;
		}
	}

	/**
	 * 检查redis 数据库单个节点是否存在
	 * 
	 * @param testRDSK
	 * @param 超时
	 * @return
	 */
	boolean testRDSK(String cmd) {
		String ss[] = cmd.split(":");
		if (ss == null || ss.length < 2 || ss.length > 3) {
			return true; // 直接返回true 表示监控
		}
		int rdb = MyFun.str2int(ss[0]);
		CRedis rds = _redispool.getRedis(s默认rd配置, rdb);

		if (rds == null)
			return true;
		if (ss.length == 2)
			return rds.exists(ss[1]);
		else if (ss.length == 3)
			return rds.hexists(ss[1], ss[2]);
		return true;
	}

	/**
	 * 检查文件是否存在
	 * 
	 * @param ci
	 * @return
	 */
	boolean testFiles(CheckItem ci) {
		String cmd[] = ci._检查内容;
		if (ci.i检查方式 == CheckItem.I_关系并且) {
			for (String s : cmd) {
				if (!MyFile.exist(s))// 只要一个不存在,就返回false
					return false;
			}
			return true;// 全部存在才返回true
		} else {
			for (String s : cmd) {
				if (MyFile.exist(s)) // 只有有一个存在,就返回true
					return true;
			}
			return false;// 全部不存在才返回false
		}

	}

	/**
	 * 检查ip和端口是否联连通
	 * 
	 * @param ci
	 * @return
	 */
	// cb._检查内容
	boolean testIpPorts(CheckItem ci) {
		String cmd[] = ci._检查内容;
		if (ci.i检查方式 == CheckItem.I_关系并且) {
			for (String s : cmd) {
				if (!MySocket.checkAddress(s, connecttimeout))
					return false;
			}
			return true;
		} else {
			for (String s : cmd) {
				if (MySocket.checkAddress(s, connecttimeout))
					return true;
			}
			return false;
		}
	}

	/**
	 * 检查HTTP URL是否联连通
	 * 
	 * @param ci
	 * @return
	 */
	boolean testUrls(CheckItem ci) {
		String cmd[] = ci._检查内容;
		if (ci.i检查方式 == CheckItem.I_关系并且) {
			for (String s : cmd) {
				if (!testUrl(s, connecttimeout))
					return false;
			}
			return true;
		} else {
			for (String s : cmd) {
				if (testUrl(s, connecttimeout))
					return true;
			}
			return false;
		}
	}

	/**
	 * 检查 http连接
	 * 
	 * @param testHttp
	 * @param 超时
	 * @return
	 */
	boolean testUrl(String testHttp, int 超时) {
		URL url = null;
		try {
			url = new URL(testHttp);
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
		try {
			URLConnection urlConn = url.openConnection();
			urlConn.setReadTimeout(超时);
			BufferedReader reader = new BufferedReader(new InputStreamReader(urlConn.getInputStream())); // 实例化输入流，并获取网页代码
			String s;
			if ((s = reader.readLine()) != null) {
				return true;
			}
		} catch (Exception e) {
			return false;
		}
		return false;
	}

	public static Thread _thread;

	public static void main(String[] args) throws Exception {
		Monitor tm = new Monitor();
		while (true) {

			if (_thread == null || !_thread.isAlive()) {
				_thread = new Thread(tm);
				_thread.setUncaughtExceptionHandler(new MyUnchecckedExceptionhandler());
				_thread.start();
				// System.out.println("异常重启线程");
			}
			// System.out.println(_thread+" "+_thread.isAlive());
			Thread.sleep(1000);
		}
	}

//关闭cmd窗口
//	public void killWin() {
//		Runtime rt = Runtime.getRuntime();
//		Process p = null;
//		try {
//			rt.exec("cmd.exe /C start taskkill /f /t /im cmd.exe");
//			// rt.exec("cmd.exe /C start wmic process where name='cmd.exe' call terminate");
//		} catch (IOException e) {
//			e.printStackTrace();
//		}
//	}

}
